using System;
using System.Collections.Generic;
using System.Text;
using System.Security.Principal;
using System.Security;

namespace InfiniTec.Security
{
    /// <summary>
    /// This allows the caching of user credential in the form of <see cref="WindowsIdentity"/> objects for
    /// a certain amount of time.
    /// </summary>
    public static class CredentialManager
    {
        private static object _SyncRoot = new object();
        private static Dictionary<string, CredentialEntry> _Cache = new Dictionary<string, CredentialEntry>(0, StringComparer.CurrentCultureIgnoreCase);
        private static TimeSpan? _SlidingExpirationTimeout = new TimeSpan(0, 5, 0);
        private static TimeSpan? _AbsoluteExpirationTimeout = new TimeSpan(1, 0, 0);

        /// <summary>
        /// Gets or sets the timeout for the sliding expiration. If SlidingExpiration is null,
        /// this feature is disabled.
        /// </summary>
        public static TimeSpan? SlidingExpirationTimeout
        {
            get { return _SlidingExpirationTimeout; }
            set { _SlidingExpirationTimeout = value; }
        }


        /// <summary>
        /// Gets or sets the timeout for the absolute expiration. If AbsoluteExpiration is null,
        /// this feature is disabled.
        /// </summary>
        public static TimeSpan? AbsoluteExpirationTimeout
        {
            get { return _AbsoluteExpirationTimeout; }
            set { _AbsoluteExpirationTimeout = value; }
        }

        /// <summary>
        /// Returns the Windows identity with the specified targetName
        /// </summary>
        /// <param name="id">The id of stored credential</param>
        /// <returns>A <see cref="WindowsIdentity"/> object, or null if no identity was stored.</returns>
        internal static CredentialEntry GetIdentity(string id)
        {

            CredentialEntry entry;

            lock (_SyncRoot)
            {
                if (!_Cache.ContainsKey(id)) return null;
                entry = _Cache[id];
            }
            if ((AbsoluteExpirationTimeout.HasValue &&
                entry.Created + AbsoluteExpirationTimeout < DateTime.Now) ||
                (SlidingExpirationTimeout.HasValue && entry.LastAccessed + SlidingExpirationTimeout < DateTime.Now))
            {

                lock (_SyncRoot)
                {
                    entry.Dispose();
                    _Cache.Remove(id);
                }

                return null;
            }
            
            return entry;
        }
        
        /// <summary>
        /// Adds a WindowsIdentity to the CredentialManager.
        /// </summary>
        /// <param name="id">The targetName for the identity</param>
        /// <param name="username">The username, either in the form 'username' or 'domain\username'</param>
        /// <param name="password">The passsword of the user</param>
        /// <param name="logonType">The <see cref="LogonType"/> to use.</param>
        /// <returns>The WindowsIdentity for the specified user</returns>
        public static CredentialEntry Add(string id, string username, SecureString password, LogonType logonType)
        {
            CredentialEntry entry;

            entry = new CredentialEntry(username, password, logonType);
            _Cache.Add(id, entry);

            return entry;
        }

        /// <summary>
        /// Adds the credential entry with the specified id to the credential manager.
        /// </summary>
        /// <param name="id">The target nmae of the entry</param>
        /// <param name="entry">The <see cref="CredentialEntry"/> to store.</param>
        /// <returns>The <see cref="CredentialEntry"/> that was specified by the entry parameter</returns>
        public static CredentialEntry Add(string id, CredentialEntry entry) 
        {
            _Cache.Add(id, entry);

            return entry;
        }



        internal static bool Contains(string id)
        {
            return _Cache.ContainsKey(id);
        }

        internal static void Remove(string targetName)
        {
            if (_Cache.ContainsKey(targetName)) _Cache.Remove(targetName);
        }
    }
}
